package com.distribute.framework.core.sql.mongodb;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import com.distribute.framework.core.sql.Column;
import com.distribute.framework.core.sql.Table;
import com.distribute.framework.core.web.helper.Utils;
import com.distribute.framework.core.web.util.Page;
import com.mongodb.AggregationOutput;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.MongoTimeoutException;

public abstract class  MongoDaoSupport<T>  extends org.springframework.dao.support.DaoSupport{
	
	MongodbDataSource dataSource;

	public MongoDaoSupport() { 
	}
	/**
	 * 构造方法，根据实例类自动获取实体类类型  
	 */
	public MongoDaoSupport(MongodbDataSource dataSource) {
		this.dataSource = dataSource;
	}

	@Override
	protected void checkDaoConfig() throws IllegalArgumentException {
		getDataSource().getMongo();
	}

	public MongodbDataSource getDataSource() {
		return dataSource;
	}

	public void setDataSource(MongodbDataSource dataSource) {
		this.dataSource = dataSource;
	}
	
	public static Set<String> checkIndexed = new HashSet();
	private DBCollection getCollection(Table tableDefine,Map params) {
		String suffix = (String) params.get("suffix");
		String tableName = tableDefine.getName()+Utils.or(suffix, "");
		DBCollection collection = getDataSource().getCollection(tableName);
		if(!checkIndexed.contains(tableName)){
			Set<String> exists = new HashSet();
			for(DBObject dbObject:collection.getIndexInfo()){
				String name = (String) dbObject.get("name");
				exists.add(name);
			}
			Boolean bModify = false;
			Map<String, Integer> indexes = tableDefine.getIndexes();
			for(Map.Entry<String, Integer> entry:indexes.entrySet()){
				String name = entry.getKey().toUpperCase();
				if(!exists.contains(name)){
					collection.createIndex(new BasicDBObject(entry.getKey(),entry.getValue()>0?1:-1), name, Math.abs(entry.getValue().intValue())==2);
					bModify = true;
				}
				exists.remove(name);
			}
			for(String name:exists){
				if(!name.startsWith("_")){
					collection.dropIndex(name);
					bModify = true;
				}
			}
			if(bModify){
				Utils.trace(tableName+" modify index",indexes);
			}
			checkIndexed.add(tableName);
		}
		return collection;
	}
	
	public static MongoDaoSupport getDao(String dataSourceNameOfProperties){
		
		return null;
	}
	
	/**
	 * 获取最新的主键
	 * @return 主键值
	 */
	private Object setNewPrimary(Table tableDefine,Map object){
		Column primaryKey = tableDefine.getPrimaryKey();
		if(primaryKey==null){//无主键
			return null;
		}else if(primaryKey.getJavaType().getSuperclass()==(Number.class)){//数字型
			DBCollection collection = getDataSource().getCollection("LZPrimary");
			//查询条件
			BasicDBObject query = new BasicDBObject("name", tableDefine.getName());
			BasicDBObject update = new BasicDBObject("$inc", new BasicDBObject("id", 1));
			DBObject document = collection.findAndModify(query, null, null, false, update, true, true);
			Long lid = Utils.parseLong(document==null?"0":document.get("id"))+1;
			Object id = Utils.parse(lid,primaryKey.getJavaType());
			try {
				object.put(primaryKey.getName(), id);
			} catch (Exception ex) {
				throw new RuntimeException(ex);
			}
			return id;
		}else if(primaryKey.getJavaType()==String.class){//字符串型
			String key = Utils.uuid();
			try {
				object.put(primaryKey.getName(), key);
			} catch (Exception ex) {
				throw new RuntimeException(ex);
			}
			return key;
		}else{
			throw new RuntimeException("无法识别的主键类型 - "+tableDefine.getName());
		}
	}
	/**
	 * 获取最新的主键
	 * @return 主键值
	 */
	private Object getPrimary(Table tableDefine,Map object){
		Column primaryKey = tableDefine.getPrimaryKey();
		if(primaryKey==null){//无主键
			return null;
		}
		try {
			return object.get(primaryKey.getName());
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
	}
	
	public Long queryCount(Table tableDefine,Map params){
		try{
			return getCollection(tableDefine,params).count(params(tableDefine,params));
		}catch(MongoTimeoutException ex){
			ex.printStackTrace();
			return 0l;
		}
	}

	static final String strOperators = "<:$lt,<=:$lte,>:$gt,>=:$gte,!=:$ne,in:$in,not:$not,like:$regex,=:$eq";
	static final Map<String,String> opts = new HashMap();
	static{
		for(String item:strOperators.split(",")){
			opts.put(item.split("\\:")[0], item.split("\\:")[1]);
		}
	}
	private BasicDBObject params(Table tableDefine,Map params) {	
		BasicDBObject paramsObject = new BasicDBObject();
		for(Column field:tableDefine.getColumns()){
			String name = field.getName();
			BasicDBObject pObject = paramsObject;
			if(params.get(name+"Start")!=null || params.get(name+"End")!=null){
				if(params.get(name+"Not")!=null){
					if(Utils.empty(params.get(name+"Not").toString()))
						pObject.put(name, pObject=new BasicDBObject());
					else
						pObject.put(name, new BasicDBObject(Utils.add(opts.get("not"), pObject=new BasicDBObject())));
				}else
					pObject.put(name, pObject=new BasicDBObject());
				if(params.get(name+"End")!=null)
					pObject.put(opts.get("<="),(Utils.parseDate(params.get(name+"End"))).getTime());
				if(params.get(name+"Start")!=null)
					pObject.put(opts.get(">="), (Utils.parseDate(params.get(name+"Start"))).getTime());
			}else if(params.get(name+"Max")!=null || params.get(name+"Min")!=null){
				if(params.get(name+"Not")!=null){
					if(Utils.empty(params.get(name+"Not").toString()))
						pObject.put(name, pObject=new BasicDBObject());
					else
						pObject.put(name, new BasicDBObject(Utils.add(opts.get("not"), pObject=new BasicDBObject())));
				}else
					pObject.put(name, pObject=new BasicDBObject());
				if(params.get(name+"Max")!=null)
					pObject.put(opts.get("<="), params.get(name+"Max"));
				if(params.get(name+"Min")!=null)
					pObject.put(opts.get(">="), params.get(name+"Min"));
			}else if(params.get(name)!=null){
				Boolean bLike = field.like;
				if(params.get(name+"Not")!=null){
					if(Utils.empty(params.get(name+"Not").toString()))
						pObject.put(name, pObject=new BasicDBObject());
					else
						pObject.put(name, new BasicDBObject(Utils.add(opts.get("not"), pObject=new BasicDBObject())));
				}else
					pObject.put(name, pObject=new BasicDBObject());
				if(bLike){
					String strValue = params.get(name).toString();
					strValue = strValue.replace("%", "").replaceAll("([\\\\\\^\\$\\*\\+\\?\\{\\}\\(\\)\\[\\]])", "\\\\$1");
					Pattern $regex = java.util.regex.Pattern.compile(strValue);
					if(params.get(name+"Not")!=null && !Utils.empty(params.get(name+"Not").toString())){
						paramsObject.put(name,new BasicDBObject(opts.get("not"),$regex));
					}else{
						paramsObject.put(name,$regex);
					}
				}else{
					pObject.put(opts.get("="),params.get(name));
				}
			}else if(params.get(name+"Not")!=null){
				if(params.get(name+"Not")!=null){
					if(Utils.empty(params.get(name+"Not").toString()))
						pObject.put(name, pObject=new BasicDBObject());
					else
						pObject.put(name, new BasicDBObject(Utils.add(opts.get("not"), pObject=new BasicDBObject())));
				}else
					pObject.put(name, pObject=new BasicDBObject());
				pObject.put(opts.get("="), null);
			}
		}
		return paramsObject;
	}
	
	
	private DBObject order(Map params) {
		BasicDBObject orderByObject = new BasicDBObject();
		String strDesc = (String)params.get("desc");
		String strAsc = (String)params.get("asc");
		if(!Utils.empty(strDesc)){
			orderByObject.put(strDesc, -1);
		}
		if(!Utils.empty(strAsc)){
			orderByObject.put(strAsc, 1);
		}
		return orderByObject;
	}
	
	public Page queryForPage(Table tableDefine,Map params, int page, int size) {
		//获取分页总数
		Long count=this.queryCount(tableDefine,params);
		//取分页的队列
		List list=this.queryForList(tableDefine,params, (int)Page.test(page,size,count), size);
		//返回一个包装分页对象
		return new Page(page,size,count,list);
	}

	public List<T> queryForList(Table tableDefine,Map params,int offset,int size){
		try{
			DBCursor cursor = getCollection(tableDefine,params).find(params(tableDefine,params)).sort(order(params)).skip(offset).limit(size);
			List<T> list = new ArrayList();
			while (cursor.hasNext()) {
				Map map = cursor.next().toMap();
				if(map.get("_id")!=null)
					map.put("_id", map.get("_id").toString());
				list.add((T) map);
			}
			return list;
		}catch(MongoTimeoutException ex){
			ex.printStackTrace();
			return new ArrayList();
		}
	}
	

	
	public T queryForObject(Table tableDefine,Map params){
		try{
			DBCursor cursor = getCollection(tableDefine,params).find(params(tableDefine,params)).sort(order(params));
			if(cursor.hasNext())
				return (T) cursor.next().toMap();
			return null;
		}catch(MongoTimeoutException ex){
			ex.printStackTrace();
			return null;
		}
	}
	
	public Map insert(Table tableDefine,Map object){
		try{
			this.setNewPrimary(tableDefine,object);//设置主键
			Map params = object;
			getCollection(tableDefine,params).insert(getSet(tableDefine,object,true));
			return object;
		}catch(MongoTimeoutException ex){
			ex.printStackTrace();
			return null;
		}
	}
	
	
	public void save(Table tableDefine,Map object){
		try{
			Map params = object;
			getCollection(tableDefine,params).update(					
				new BasicDBObject(Utils.add(tableDefine.getPrimaryName(), this.getPrimary(tableDefine,object))), 
				getSet(tableDefine,params,false)
			);
		}catch(MongoTimeoutException ex){
			ex.printStackTrace();
		}
	}
	
	/**
	 * 更新非空字段
	 * @param object
	 * @return
	 */
	public int update(Table tableDefine,Map object){
		try{
			Map params = object;
			return getCollection(tableDefine,params).update(
				new BasicDBObject(Utils.add(tableDefine.getPrimaryName(), this.getPrimary(tableDefine,object))), 
				new BasicDBObject(Utils.add("$set", getSet(tableDefine,object,true)))
			).getN();
		}catch(MongoTimeoutException ex){
			ex.printStackTrace();
			return -1;
		}
	}
	
	private DBObject getSet(Table tableDefine,Map values,Boolean notnull) {
		BasicDBObject setObject = new BasicDBObject();
		for(Column field:tableDefine.getColumns()){
			String key = field.getName();
			if(values.get(key)!=null || !notnull){
				setObject.put(key, Utils.parse(values.get(key), field.getJavaType()));
			}
		}
		return setObject;
	}

	public int update(Table tableDefine,Map params,Map values){
		try{
			if(params(tableDefine,params).isEmpty())
				throw new RuntimeException("条件为空，不能更新数据");
			DBObject sets = getSet(tableDefine,values,true);
			if(sets.toMap().isEmpty())
				throw new RuntimeException("Set为空，不能更新数据");
			return getCollection(tableDefine,params).update(
				params(tableDefine,params),
				new BasicDBObject(Utils.add("$set", sets)),
				false,
				true
			).getN();
		}catch(MongoTimeoutException ex){
			ex.printStackTrace();
			return -1;
		}
	}
	
	public void delete(Table tableDefine,Map params){
		try{
			if(params(tableDefine,params).isEmpty() && !(""+params.get("confirm")).equals("all"))
				throw new RuntimeException("条件为空，不能删除数据");
			getCollection(tableDefine,params).remove(params(tableDefine,params));
		}catch(MongoTimeoutException ex){
			ex.printStackTrace();
		}
	}

}